﻿using System;
using System.Diagnostics.Contracts;
using System.Xml.Serialization;

namespace MathService
{
    /// <summary>Комплексное число</summary>
    [Serializable]
    [System.ComponentModel.TypeConverter(typeof(ComplexConverter))]
    public partial struct Complex : IEquatable<Complex>, ICloneable<Complex>, IFormattable
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Точность вычисления тригонометрических функций 1.11022302462516e-16 </summary>
        /// <remarks>Определено приближённо</remarks>
        public const double Epsilon = 2.0e-16;

        public static Complex Sin(Complex z)
        {
            var re = z.Re;
            var im = z.Im;
            var sin_a = Math.Sin(re);
            var cos_a = Math.Cos(re);
            var exp_b = Math.Exp(im);
            exp_b -= 1 / exp_b;

            return new Complex(.5 * sin_a * exp_b, .5 * cos_a * exp_b);
        }

        public static Complex Cos(Complex z)
        {
            var re = z.Re;
            var im = z.Im;
            var sin_a = Math.Sin(re);
            var cos_a = Math.Cos(re);
            var exp_b = Math.Exp(im);
            var exp_b_inv = 1 / exp_b;

            return new Complex(.5 * cos_a * (exp_b - exp_b_inv), -.5 * sin_a * (exp_b + exp_b_inv));
        }

        public static Complex Ln(double Im)
        {
            return new Complex(Math.Log(Im), Math.Abs(Im) < double.Epsilon ? 0 : (Im > 0 ? Consts.pi05 : -Consts.pi05));
        }

        ///<summary>НАтуральный логорифм комплексного числа</summary>
        ///<param name="z">Комплексное число</param>
        ///<returns>Натуральный логорифм</returns>
        public static Complex Ln(Complex z)
        {
            return new Complex(.5 * Math.Log(z.f_Re * z.f_Re + z.f_Im * z.f_Im), z.Arg);
        }

        ///<summary>Логорифм мномого числа по действительному основанию</summary>
        ///<param name="Im">Мнимое число</param>
        ///<param name="b">Действительное основание логорифма</param>
        ///<returns>Логорифм мнимого числа по действительному основанию</returns>
        public static Complex Log(double Im, double b)
        {
            return new Complex(
                Math.Log(Im, b),
                Math.Abs(Im) < double.Epsilon ? 0 : (Im > 0 ? Consts.pi05 : -Consts.pi05) * Math.Log(Math.E, b));
        }

        /// <summary>Логарифм комплексного числа по действительному аргументу</summary>
        /// <param name="z">Комплексное число</param>
        /// <param name="b">Действительное основание логорифма</param>
        /// <returns>Логорифм комплексного числа по действительному основанию</returns>
        public static Complex Log(Complex z, double b)
        {
            return new Complex(.5 * Math.Log(z.f_Re * z.f_Re + z.f_Im * z.f_Im, b), z.Arg * Math.Log(Math.E, b));
        }

        /// <summary>Экспоненциальная форма числа Z = e^j*Arg</summary>
        /// <param name="Arg">Аргумент</param>
        /// <returns>Комплексное число в экспоненциальной форме записи</returns>
        public static Complex Exp(double Arg) { return new Complex(Math.Cos(Arg), Math.Sin(Arg)); }

        /// <summary>Экспоненциальная форма числа Z = Abs * e^j*Arg</summary>
        /// <param name="Abs">Модуль числа</param>
        /// <param name="Arg">Аргумент числа</param>
        /// <returns>Комплексное число в экспоненциальной форме</returns>
        public static Complex Exp(double Abs, double Arg)
        {
            return new Complex(Abs * Math.Cos(Arg), Abs * Math.Sin(Arg));
        }

        ///// <summary>Алгебраическая форма записи комплексного числа</summary>
        ///// <param name="Re">Действительная часть числа</param>
        ///// <returns>Комплексное число в алгебраической форме записи</returns>
        //public static Complex Mod(double Re) { return new Complex(Re); }

        /// <summary>Алгебраическая форма записи комплексного числа</summary>
        /// <param name="Re">Действительная часть числа</param>
        /// <param name="Im">Мнимиая часть числа</param>
        /// <returns>Комплексное число в алгебраической форме записи</returns>
        public static Complex Mod(double Re, double Im = 0) { return new Complex(Re, Im); }

        /// <summary>Действительное "комплексное" число</summary>
        public static Complex Real { get { return new Complex(1); } }

        /// <summary>Мнимая единица</summary>
        public static Complex i { get { return new Complex(0, 1); } }

        ///<summary>Создать массив комплексных чисел</summary>
        ///<param name="Re">Массив действительных чисел</param>
        ///<param name="Im">Массив мнимых чисел</param>
        ///<returns>Массив комплексных чисел</returns>
        public static Complex[] CreateArray(double[] Re, double[] Im)
        {
            Contract.Requires<ArgumentException>(Re.Length == Im.Length, "Размеры масивов не равны");
            return new Complex[Re.Length].Initialize(j => new Complex(Re[j], Im[j]));
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Действительная часть</summary>
        private double f_Re;

        /// <summary>Мнимая часть</summary>
        private double f_Im;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Действительная часть</summary>
        public double Re
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_Re; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_Re = value; }
        }

        /// <summary>Мнимая часть</summary>
        public double Im
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_Im; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_Im = value; }
        }

        /// <summary>X * X^* = Re(X)^2 + Im(X)^2</summary>
        [XmlIgnore]
        public double Power
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_Re * f_Re + f_Im * f_Im; }
        }

        /// <summary>Модуль</summary>
        [XmlIgnore]
        public double Abs
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return Math.Sqrt(Power); }
            [System.Diagnostics.DebuggerStepThrough]
            set { SetAbsArg(value, Arg); }
        }

        /// <summary>Аргумент</summary>
        [XmlIgnore]
        public double Arg
        {
            [System.Diagnostics.DebuggerStepThrough]
            get
            {
                return Math.Abs(f_Re) < double.Epsilon
                    ? (Math.Abs(f_Im) < double.Epsilon      // Re == 0
                        ? 0                                 //  Im == 0 => 0
                        : Math.Sign(f_Im) * Consts.pi05)    //  Im != 0 => pi/2 * sign(Im)
                    : (Math.Abs(f_Im) < double.Epsilon      // Re != 0
                        ? (Math.Sign(f_Re) > 0
                            ? 0
                            : Consts.pi)
                        : Math.Atan2(f_Im, f_Re));          //  Im != 0 => atan(Im/Re)
            }
            [System.Diagnostics.DebuggerStepThrough]
            set { SetAbsArg(Abs, value); }
        }

        /// <summary>Комплексно сопряжённое число</summary>
        [XmlIgnore]
        public Complex ComplexConjugate
        {
            [Pure, System.Diagnostics.DebuggerStepThrough]
            get { return new Complex(Re, -Im); }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                Re = value.Re;
                Im = -value.Im;
            }
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Комплексное число</summary>
        /// <param name="Re">Действительная часть</param>
        /// <param name="Im">Мнимая часть</param>
        [System.Diagnostics.DebuggerStepThrough]
        public Complex(double Re, double Im = 0)
        {
            f_Re = Re;
            f_Im = Im;
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Установить действительную и мнимую часть</summary>
        /// <param name="re">Действительная часть</param>
        /// <param name="im">Мнимая часть</param>
        [System.Diagnostics.DebuggerStepThrough]
        public void SetReIm(double re, double im = 0)
        {
            f_Re = re;
            f_Im = im;
        }

        /// <summary>Установить модуль и аргумент</summary>
        /// <param name="abs">Модуль</param>
        /// <param name="arg">Аргумент</param>
        [System.Diagnostics.DebuggerStepThrough]
        public void SetAbsArg(double abs, double arg = 0)
        {
            f_Re = abs * Math.Cos(arg);
            f_Im = abs * Math.Sin(arg);
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Строковый эквивалент</summary>
        /// <returns>Строковый эквивалент</returns>
        [System.Diagnostics.DebuggerStepThrough]
        public override string ToString()
        {
            if(Math.Abs(Re) < double.Epsilon && Math.Abs(Im) < double.Epsilon) return "0";
            var lv_Real = Re.ToString();
            var lv_Imagen = string.Format("{0}i", Math.Abs(Math.Abs(Im) - 1) > double.Epsilon ? Math.Abs(Im).ToString() : "");
            if(Im < 0) lv_Imagen = "-" + lv_Imagen;
            return string.Format("{0}{1}",
                Math.Abs(Re) > double.Epsilon
                    ? string.Format("{0}{1}", lv_Real, Im > 0 ? "+" : "")
                    : "",
                Math.Abs(Im) > double.Epsilon ? lv_Imagen : "");
        }

        [System.Diagnostics.DebuggerStepThrough]
        public string ToString(string Format)
        {
            if(Math.Abs(Re) < double.Epsilon && Math.Abs(Im) < double.Epsilon) return "0";
            var lv_Real = Re.ToString(Format);
            var lv_Imagen = string.Format("{0}i",
                Math.Abs(Math.Abs(Im) - 1) > double.Epsilon ? Math.Abs(Im).ToString(Format) : "");
            if(Im < 0) lv_Imagen = "-" + lv_Imagen;
            return string.Format("{0}{1}",
                Math.Abs(Re) > double.Epsilon
                    ? string.Format("{0}{1}", lv_Real, Im > 0 ? "+" : "")
                    : "",
                Math.Abs(Im) > double.Epsilon ? lv_Imagen : "");
        }

        [System.Diagnostics.DebuggerStepThrough]
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if(Math.Abs(Re) < double.Epsilon && Math.Abs(Im) < double.Epsilon) return "0";
            var lv_Real = Re.ToString(format, formatProvider);
            var lv_Imagen = string.Format("{0}i",
                Math.Abs(Math.Abs(Im) - 1) > double.Epsilon ? Math.Abs(Im).ToString(format, formatProvider) : "");
            if(Im < 0) lv_Imagen = "-" + lv_Imagen;
            return string.Format("{0}{1}",
                Math.Abs(Re) > double.Epsilon
                    ? string.Format("{0}{1}", lv_Real, Im > 0 ? "+" : "")
                    : "",
                Math.Abs(Im) > double.Epsilon ? lv_Imagen : "");
        }

        /// <summary>Хеш-код числа</summary>
        /// <returns>Хеш-код числа</returns>
        [System.Diagnostics.DebuggerStepThrough]
        public override int GetHashCode() { unchecked { return (f_Re.GetHashCode() * 0x18d) ^ f_Im.GetHashCode(); } }

        [System.Diagnostics.DebuggerStepThrough]
        public Complex Clone() { return new Complex(f_Re, f_Im); }

        [System.Diagnostics.DebuggerStepThrough]
        object ICloneable.Clone() { return Clone(); }

        /// <summary>Сравнение с другим объектом</summary>
        /// <param name="obj">Сравниваемый объект</param>
        /// <returns>Истина, если объект - комплексное число и чдействительная и мнимая части равны</returns>
        [System.Diagnostics.DebuggerStepThrough]
        public override bool Equals(object obj)
        {
            return (obj is Complex) && Equals((Complex)obj);
        }

        #region IEquatable<Complex> Members

        [System.Diagnostics.DebuggerStepThrough]
        public bool Equals(Complex other)
        {
            return Math.Abs(f_Re - other.f_Re) < double.Epsilon
                && Math.Abs(f_Im - other.f_Im) < double.Epsilon;
        }

        [System.Diagnostics.DebuggerStepThrough]
        bool IEquatable<Complex>.Equals(Complex other) { return Equals(other); }

        #endregion

        [System.Diagnostics.DebuggerStepThrough]
        public Complex Inc(double Re, double Im)
        {
            f_Re += Re;
            f_Im += Im;
            return this;
        }


        [System.Diagnostics.DebuggerStepThrough]
        public Complex Inc(Complex z)
        {
            Inc(z.Re, z.Im);
            return this;
        }

        [System.Diagnostics.DebuggerStepThrough]
        public Complex Dec(double Re, double Im)
        {
            f_Re -= Re;
            f_Im -= Im;
            return this;
        }

        [System.Diagnostics.DebuggerStepThrough]
        public Complex Dec(Complex z)
        {
            Dec(z.Re, z.Im);
            return this;
        }

        [System.Diagnostics.DebuggerStepThrough]
        public Complex Rotate(double w)
        {
            var sin = Math.Sin(w);
            var cos = Math.Cos(w);
            var re = f_Re;
            var im = f_Im;

            return new Complex(f_Re = re * cos - im * sin, f_Im = re * cos + im * sin);
        }

        public Complex Round(int DigitsCount)
        {
            return new Complex(Math.Round(f_Re, DigitsCount), Math.Round(f_Im, DigitsCount));
        }

        //[ContractInvariantMethod]
        //private void ContractInveriantMethod()
        //{
        //    Contract.Invariant(!double.IsNaN(f_Re));
        //    Contract.Invariant(!double.IsNaN(f_Im));
        //}

    }
}
